#include "polygon_renderer.h"
#include <GL/glew.h>
#include "engine/utils/file_utils.h"

namespace kanon
{
    PolygonBufferBatch::~PolygonBufferBatch()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteBuffers(1, &vbo);
    }

    PolygonBufferBatch::PolygonBufferBatch(uint32_t max_segments)
    {
        max_vertices = max_segments * 2;
        num_vertices = 0;

        vertex_base = nullptr;

        // create vao and vbo
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, max_vertices * sizeof(PolygonVertex), nullptr, GL_DYNAMIC_DRAW);

        // set vertex attributes
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(PolygonVertex), (void*)offsetof(PolygonVertex, pos));
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(PolygonVertex), (void*)offsetof(PolygonVertex, color));
        glEnableVertexAttribArray(1);
    }

    void PolygonBufferBatch::Begin()
    {
        // map buffer
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        vertex_base = (PolygonVertex*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        num_vertices = 0;
    }

    void PolygonBufferBatch::End()
    {
        glUnmapBuffer(GL_ARRAY_BUFFER);
        vertex_base = nullptr;
    }

    bool PolygonBufferBatch::Submit(const glm::vec2& pt1, const glm::vec2& pt2, const glm::vec4& color)
    {
        uint32_t particle_vertices = 2;
        if (num_vertices + particle_vertices > max_vertices)
            return false;

        // copy vertex data
        PolygonVertex* data = vertex_base + num_vertices;
        data[0].pos = pt1;
        data[0].color = color;
        data[1].pos = pt2;
        data[2].color = color;

        num_vertices += 2;
        return true;
    }

    PolygonRenderer::PolygonRenderer()
    {
        std::string vertex_shader_path = FileUtils::getShaderPath("sprite_renderer.vs");
        std::string fragment_shader_path = FileUtils::getShaderPath("sprite_renderer.fs");
        shader = Shader::FromShaderFiles(vertex_shader_path, fragment_shader_path);
        batch = std::make_shared<PolygonBufferBatch>(50000);
    }

    void PolygonRenderer::Render(const glm::vec2& pt1, const glm::vec2& pt2, const glm::vec4& color)
    {
        bool success = batch->Submit(pt1, pt2, color);
        if (!success)
        {
            this->Flush();
            this->Begin();
            batch->Submit(pt1, pt2, color);
        }
    }

    void PolygonRenderer::Render(const std::vector<glm::vec2>& points, const glm::vec4& color, bool loop)
    {
        uint32_t npoints = static_cast<uint32_t>(points.size());
        for (uint32_t index = 0; index < npoints - 1; ++index)
            Render(points[index], points[index + 1], color);

        if (loop)
            Render(points[npoints - 1], points[0], color);
    }

    void PolygonRenderer::Begin()
    {
        batch->Begin();
    }

    void PolygonRenderer::End()
    {
        this->Flush();
    }

    void PolygonRenderer::SetCamera(const std::shared_ptr<Camera2d>& cam)
    {
        this->camera = cam;
    }

    void PolygonRenderer::SetLineWidth(float width)
    {
        glLineWidth(width);
    }

    void PolygonRenderer::Flush()
    {
        batch->End();
        shader->Bind();
        if (this->camera)
        {
            glm::mat4 view = this->camera->GetViewMatrix();
            glm::mat4 proj = this->camera->GetProjectionMatrix();
            shader->set_matrix("vp", proj * view);
        }
        glDrawArrays(GL_LINES, 0, batch->num_vertices);
        shader->Unbind();
    }
}